一、阴影的几种实现
投影阴影:
- 给定一个物体、一个光源、一个平面,生成一个变换矩阵。
- 把物体上的点变换为平面上的点,再把阴影多边形绘制出来。
- 优缺点:高效易于实现,但是只适用于平坦表面。
阴影体:
- 找到阴影体。
- 判断相交部分,进行对应的处理。
- 优缺点:高度准确,不易走样。但是计算代价大。
阴影贴图
最实用最流行的阴影实现方式。
二、阴影贴图实现
声明相关变量:
int scSizeX, scSizeY;
GLuint shadowTex, shadowBuffer;
glm::mat4 lightVmatrix;
glm::mat4 lightPmatrix;
glm::mat4 shadowMVP1;
glm::mat4 shadowMVP2;
glm::mat4 b;
自定义一个阴影的帧缓冲区:
void init(GLFWwindow * window)
{
//...
//setupVertexBuffers();//顶点缓冲区
setupShadowBuffers(window);//阴影帧缓冲区
//...
}
void setupShadowBuffers(GLFWwindow* window)
{
//定义b矩阵用于将光源空间变换到纹理贴图空间
b = glm::mat4(
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f);
glfwGetFramebufferSize(window, &width, &height);
scSizeX = width;
scSizeY = height;
glGenFramebuffers(1, &shadowBuffer);
glGenTextures(1, &shadowTex);
glBindTexture(GL_TEXTURE_2D, shadowTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32,
scSizeX, scSizeY, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
// may reduce shadow border artifacts
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
使用两个Pass来渲染:
- 第一个Pass: 渲染阴影贴图
- 第二个Pass: 渲染图形
void display(GLFWwindow * window, double currentTime)
{
//...
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
//...
//光源空间矩阵构建(定向光)
lightVmatrix = glm::lookAt(cam.e, cam.e + light.dir, up);
lightPmatrix = glm::ortho(-20.0f, 20.0f, -20.0f, 20.0f, -20.0f, 20.0f);
//********** PASS 1 ******************************
//使用自定义的阴影帧缓冲区,并把阴影纹理附着在上面
glBindFramebuffer(GL_FRAMEBUFFER, shadowBuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowTex, 0);
glDrawBuffer(GL_NONE); //关闭绘制颜色
glEnable(GL_DEPTH_TEST); //开启深度测试
glEnable(GL_POLYGON_OFFSET_FILL); // 开启深度偏移
glPolygonOffset(2.0f, 4.0f); // 深度偏移
passOne();
glDisable(GL_POLYGON_OFFSET_FILL); // 关闭深度偏移
//***************************************************
//********** PASS 2 ******************************
//使用显示的帧缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glActiveTexture(GL_TEXTURE0); //纹理单元0-绑定阴影纹理
glBindTexture(GL_TEXTURE_2D, shadowTex);
glActiveTexture(GL_TEXTURE1); //纹理单元1-绑定物体纹理
glBindTexture(GL_TEXTURE_2D, myTexture);
glDrawBuffer(GL_FRONT);//重新开启绘制颜色
passTwo();
//***************************************************
}
其中两个Pass的定义:
//pass 1
void passOne(void)
{
glUseProgram(renderingProgram1);
// 绘制当前模型
mMat = glm::translate(glm::mat4(1.0f), modelPos);
mMat = glm::rotate(mMat, toRadians(25.0f), glm::vec3(1.0f, 0.0f, 0.0f));
shadowMVP1 = lightPmatrix * lightVmatrix;
sLoc = glGetUniformLocation(renderingProgram1, "shadowMVP");
glUniformMatrix4fv(sLoc, 1, GL_FALSE, glm::value_ptr(shadowMVP1));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());
// 绘制其他模型....
}
//pass 2
void passTwo(void)
{
glUseProgram(renderingProgram2);
// 绘制当前模型
vMat = cam.GetMatrixV();
mMat = glm::translate(glm::mat4(1.0f), modelPos);
mvMat = vMat * mMat;
invTrMat = transpose(inverse(mvMat));//逆转置矩阵
shadowMVP2 = b * lightPmatrix * lightVmatrix;//b矩阵用于将光源空间变换到纹理贴图空间
mLoc = glGetUniformLocation(renderingProgram2, "m_matrix");
vLoc = glGetUniformLocation(renderingProgram2, "v_matrix");
mvLoc = glGetUniformLocation(renderingProgram2, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram2, "proj_matrix");
nLoc = glGetUniformLocation(renderingProgram2, "norm_matrix");
sLoc = glGetUniformLocation(renderingProgram2, "shadowMVP");
ambLoc = glGetUniformLocation(renderingProgram2, "ambient");
dirLightDirLoc = glGetUniformLocation(renderingProgram2, "light.dir");
dirLightColorLoc = glGetUniformLocation(renderingProgram2, "light.color");
mDiffLoc = glGetUniformLocation(renderingProgram2, "material.diffuse");
mSpecLoc = glGetUniformLocation(renderingProgram2, "material.specular");
mGlosLoc = glGetUniformLocation(renderingProgram2, "material.gloss");
glUniformMatrix4fv(mLoc, 1, GL_FALSE, glm::value_ptr(mMat));
glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat));
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
glUniformMatrix4fv(sLoc, 1, GL_FALSE, glm::value_ptr(shadowMVP2));
glUniform4fv(ambLoc, 1, glm::value_ptr(ambientColor));
glUniform4fv(dirLightColorLoc, 1, glm::value_ptr(light.color));
glUniform3fv(dirLightDirLoc, 1, glm::value_ptr(light.dir));
glUniform4fv(mDiffLoc, 1, glm::value_ptr(materialDiff));
glUniform4fv(mSpecLoc, 1, glm::value_ptr(materialSpec));
glUniform1fv(mGlosLoc, 1, &materialGloss);
//传递顶点属性-顶点位置
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
//传递顶点属性-顶点纹理坐标
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
//传递顶点属性-顶点法线
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(2);
//设置
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
//绘制图形
glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());
// 绘制其他模型....
}
渲染阴影纹理(Pass1)使用的着色器代码:
//顶点着色器(仅变换到光源空间)
#version 430
layout (location=0) in vec3 vertPos;
uniform mat4 shadowMVP;
void main(void)
{ gl_Position = shadowMVP * vec4(vertPos,1.0);
}
//片段着色器(不执行任何操作,仅深度写入)
#version 430
void main(void) {}
第二轮渲染(Pass2)的着色器中加上以下代码:
//顶点着色器
//...
uniform mat4 shadowMVP;
layout (binding = 0) uniform sampler2DShadow shadowTex;
out vec4 shadow_coord;
//...
void main(void)
{
//...
shadow_coord = shadowMVP * vec4(position,1.0);
//...
}
//片段着色器
//...
uniform mat4 shadowMVP;
layout(binding = 0) uniform sampler2DShadow shadowTex;
in vec4 shadow_coord;
//...
void main(void)
{
//...
float inShadow = textureProj(shadowTex, shadow_coord);
if (inShadow != 0.0)
{
color = vec4((ambient.xyz + diffuse + specular), 1.0);
}
else
{
color = vec4((ambient.xyz), 1.0);
}
}
关于阴影痤疮和偏移(Bias)设置:
可以使用glPolygonOffset(value, value);
来设置偏移量来避免阴影痤疮。
需要开启深度偏移。(glEnable(GL_POLYGON_OFFSET_FILL);
)
渲染完成后关闭。(glDisable(GL_POLYGON_OFFSET_FILL);
)
关于柔和阴影:
使用百分比临近滤波(PCF)。
顶点着色器修改:
float sampShadowTex(float ox, float oy)
{
//把(1 / windowsize)的值硬编码为0.001。
//第三个参数也可以避免阴影痤疮。(和glPolygonOffset()作用一样)
return textureProj(shadowTex, shadow_coord + vec4(ox * 0.001 * shadow_coord.w, oy * 0.001 * shadow_coord.w, -0.005, 0.0));
}
//......
float shadowFactor = 0.0;
float swidth = 1.0;
vec2 offset = mod(floor(gl_FragCoord.xy), 2.0);//抖动值。有四个值(0,0)(0,1)(1,0)(1,1)
shadowFactor += sampShadowTex((-1.5 + offset.x) * swidth, (1.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((-1.5 + offset.x) * swidth, (-0.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((0.5 + offset.x) * swidth, (1.5 - offset.y) * swidth);
shadowFactor += sampShadowTex((0.5 + offset.x) * swidth, (-0.5 - offset.y) * swidth);
shadowFactor = shadowFactor / 4.0;
//......
color = vec4(ambient + shadowFactor * (diffuse + specular), 1.0);